# -*- coding: utf-8 -*-
import MySQLdb

from Exceptions import LibraryException

"""
Die Datenbank-Klasse dient dazu, die Verbindung mit der Datenbank herzustellen.

@author: Philipp Dermitzel
@date: 2011-03-28
@version: 1.4.2
@todo: Exceptions beim Verbindungsaufbau (nicht gestarteter MySQL-Server z. B.)
    und bei commit/rollback/...
    Und: Erstellung der Medien-Tabellen in der Datenbank
"""

class Database(object):
    
    """
    ---------------------------------------------------------------------------
    Klassenvariablen
    ---------------------------------------------------------------------------
    """
    
    """
    Speichert alle Informationen für den Verbindungsaufbau zur Datenbank.
    @type: Dictionary
    """
    __dbData = {
        'hostname' : 'localhost',
        'username' : 'mtmc',
        'password' : 'mtmc',
        'database' : 'mediaCenter',
        'unixSocket' : '/opt/lampp/var/mysql/mysql.sock' #optional
    }
    
    """
    Die Datenbank-Verbindung. Wird nur intern benötigt.
    @type: Connection
    """
    connection = None
    
    """
    Der Cursor, über den die Datenbank angesprochen werden kann.
    @type: Cursor
    """
    dbCursor = None
    
    """
    ---------------------------------------------------------------------------
    Konstruktor
    ---------------------------------------------------------------------------
    """
    
    """
    Der Konstruktor. Stellt die Verbindung zur Datenbank her und speichert den
    Cursor in der Klassenvariable.
    """
    def __init__(self, createNewDBs = False):
        if not createNewDBs:
            if self.__dbData['unixSocket'] is not None:
                self.connection = MySQLdb.connect(self.__dbData['hostname'], 
                    self.__dbData['username'], self.__dbData['password'], 
                    self.__dbData['database'], 
                    unix_socket=self.__dbData['unixSocket'])
            else: 
                self.connection = MySQLdb.connect(self.__dbData['hostname'], 
                    self.__dbData['username'], self.__dbData['password'], 
                    self.__dbData['database'])      
            self.dbCursor = self.connection.cursor()
        else: self.__createMediaDBs()
    
    """
    ---------------------------------------------------------------------------
    Methoden der Klasse
    ---------------------------------------------------------------------------
    """   
    
    """
    Fuehrt einen Befehl aus. Dieser wird als Transaktion nach Ausführung per
    commit geschrieben.
    @param statement: Das SQL-Statement.
    @type statement: String
    """
    def execute(self, statement):
        #print statement
        self.dbCursor.execute(statement)
        self.connection.commit() 
    
    """
    Speichert einen Datensatz in der Datenbank. Hierbei ist zu beachten, dass
    None-Werte in NULL-Werte gewandelt und Strings sowohl normalisiert als auch
    mit einfachen Anführungszeichen versehen werden. Die Übergabedaten sind
    also Rohdaten!    
    @param table: Die Tabelle, in die eingefügt werden soll.
    @type table: String
    @param arguments: Alle Key-Value-Zuweisungen zum Einfügen in die Datenbank.
    @type arguments: Dictionary
    """
    def insert(self, table, arguments):
        for k, v in arguments.iteritems():
            if v is None:
                arguments[k] = 'NULL'
            elif isinstance(v, str):
                arguments[k] = "'%s'" %(self.normalizeString(v))
        keys = ''.join(['%s, ' %(k) for k in arguments])
        values = ''.join(['%s, ' %(v) for k,v in arguments.iteritems()])
        self.execute('INSERT INTO %s (%s) VALUES (%s)' % (table, keys[0:-2], 
            values[0:-2]))
    
    """
    Liefert einen Datensatz aus der Datenbank. Hierbei ist zu beachten, dass
    None-Werte in NULL-Werte gewandelt und Strings sowohl normalisiert als auch
    mit einfachen Anführungszeichen versehen werden. Die Übergabedaten sind
    also Rohdaten!
    @param columns: Die Spalten, die ausgelesen werden
    @type columns: String
    @param table: Die Tabelle, aus der ausgelesen werden soll.
    @type table: String
    @param arguments: Alle Key-Value-Zuweisungen zum Einfügen in die Datenbank.
    @type arguments: Dictionary
    @param add: Anhänge an den SQL-String, z.B. ORDER BY
    @type add: String
    """    
    def select(self, columns, table, arguments, add = ''):
        if len(arguments) > 0:
            for k, v in arguments.iteritems():
                if v is None:   
                    arguments[k] = 'NULL'
                elif isinstance(v, str):
                    arguments[k] = "'%s'" %(self.normalizeString(v))
            where = ''.join(['%s=%s AND ' %(k,v) for k,v in 
                arguments.iteritems()])
            where = "WHERE (%s)" %(where[0:-5])
        else: where = ''
        self.execute('SELECT %s FROM %s %s %s' % (columns, table, where, add))
    
    """
    Updatet einen Datensatz in der Datenbank. Hierbei ist zu beachten, dass
    None-Werte in NULL-Werte gewandelt und Strings sowohl normalisiert als auch
    mit einfachen Anführungszeichen versehen werden. Die Übergabedaten sind
    also Rohdaten! 
    @param table: Die Tabelle, in welcher das Update durchgeführt werden soll.
    @type table: String
    @param arguments: Alle Key-Value-Zuweisungen zum Update in die Datenbank.
    @type arguments: Dictionary
    @param whereArgs: Alle Key-Value-Zuweisungen zum Identifizieren der Daten-
        sätze, auf denen das Update durchgeführt werden soll (WHERE).
    @type whereArgs: Dictionary
    """      
    def update(self, table, arguments, whereArgs):
        for k, v in arguments.iteritems():
            if v is None:
                arguments[k] = 'NULL'
            elif isinstance(v, str):
                arguments[k] = "'%s'" %(self.normalizeString(v))
        for l, w in whereArgs.iteritems():
            if w is None:
                whereArgs[l] = 'NULL'
            elif isinstance(w, str):
                whereArgs[l] = "'%s'" %(self.normalizeString(w))
        update = ''.join(['%s=%s, ' %(k,v) for k,v in arguments.iteritems()])
        where = ''.join(['%s=%s AND ' %(l,w) for l,w in whereArgs.iteritems()])
        self.execute('UPDATE %s SET %s WHERE (%s)' % (table, update[0:-2],
            where[0:-5]))
    
    """
    Löscht einen Datensatz aus der Datenbank. Hierbei ist zu beachten, dass
    None-Werte in NULL-Werte gewandelt und Strings sowohl normalisiert als auch
    mit einfachen Anführungszeichen versehen werden. Die Übergabedaten sind
    also Rohdaten!
    @param table: Die Tabelle, aus der gelöscht werden soll.
    @type table: String
    @param arguments: Alle Key-Value-Zuweisungen zum Auffinden der Datensätze.
    @type arguments: Dictionary
    @param add: Anhänge an den SQL-String, z.B. LIMIT
    @type add: String
    """        
    def delete(self, table, arguments, add = ''):
        for k, v in arguments.iteritems():
            if v is None:
                arguments[k] = 'NULL'
            elif isinstance(v, str):
                arguments[k] = "'%s'" %(self.normalizeString(v))
        where = ''.join(['%s=%s AND ' %(k,v) for k,v in arguments.iteritems()])
        self.execute('DELETE FROM %s WHERE (%s) %s' %(table, where[0:-5], add))
    
    """
    Erstellt einen neuen Savepoint.
    @param statement: Das Name des Savepoints.
    @type statement: String
    """ 
    def savepoint(self, name):
        self.dbCursor.execute("SAVEPOINT %s" %(name))     
    
    """
    Führt einen Rollback zum gegebenen Savepoint aus.
    @param statement: Das Name des Savepoints.
    @type statement: String
    """    
    def rollback(self, name):
        try:
            self.dbCursor.execute("ROLLBACK TO SAVEPOINT %s" %(name))
        except:
            print "savepoint nicht vorhanden"    
    
    """
    Erstellt die für das Programm benötigten Tabellen in der Datenbank.
    """
    def __createMediaDBs(self): 
        if self.__dbData['unixSocket'] is not None:
            self.connection = MySQLdb.connect(self.__dbData['hostname'], 
                self.__dbData['username'], self.__dbData['password'], 'mysql', 
                unix_socket=self.__dbData['unixSocket'])
        else: 
            self.connection = MySQLdb.connect(self.__dbData['hostname'], 
                self.__dbData['username'], self.__dbData['password'], 'mysql')      
        self.dbCursor = self.connection.cursor()
        
        try:
            self.execute("CREATE DATABASE IF NOT EXISTS %s" 
                %(self.__dbData['database']))
            self.execute("USE %s" %(self.__dbData['database']))
            self.execute("""
            CREATE TABLE IF NOT EXISTS `Artist` (
                `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
                `name` varchar(250) NOT NULL,
                `sort` varchar(250) DEFAULT NULL,
                `imgPath` varchar(1000) DEFAULT NULL,
                `mbId` varchar(50) DEFAULT NULL,
                PRIMARY KEY (`id`)
            ) ENGINE=InnoDB  DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ;
            """)
            self.execute("""
            CREATE TABLE IF NOT EXISTS `Cd` (
                `id` int(11) NOT NULL AUTO_INCREMENT,
                `name` varchar(500) NOT NULL,
                `artist` int(11) DEFAULT NULL,
                `year` int(11) DEFAULT NULL,
                `imgPath` varchar(1000) DEFAULT NULL,
                `mbId` varchar(50) DEFAULT NULL,
                PRIMARY KEY (`id`)
            ) ENGINE=InnoDB  DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ;
            """)
            self.execute("""
            CREATE TABLE IF NOT EXISTS `Genre` (
                `id` int(11) NOT NULL AUTO_INCREMENT,
                `name` varchar(500) NOT NULL,
                PRIMARY KEY (`id`)
            ) ENGINE=InnoDB  DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ;
            """)
            self.execute("""
            CREATE TABLE IF NOT EXISTS `hasGenre` (
                `cd` int(11) NOT NULL,
                `genre` int(11) NOT NULL,
                PRIMARY KEY (`cd`,`genre`)
            ) ENGINE=InnoDB DEFAULT CHARSET=latin1;
            """)
            self.execute("""
            CREATE TABLE IF NOT EXISTS `Track` (
                `id` int(11) NOT NULL AUTO_INCREMENT,
                `name` varchar(500) DEFAULT NULL,
                `artist` int(11) DEFAULT NULL,
                `cd` int(11) DEFAULT NULL,
                `trackOnCd` int(11) DEFAULT NULL,
                `length` int(11) NOT NULL,
                `path` varchar(1000) NOT NULL,
                PRIMARY KEY (`id`)
            ) ENGINE=InnoDB  DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ;
            """)
            self.execute("""
            CREATE TABLE IF NOT EXISTS `Settings` (
                `settingKey` varchar(100) NOT NULL,
                `settingValue` varchar(500) DEFAULT NULL,
                PRIMARY KEY (`settingKey`)
            ) ENGINE=MyISAM DEFAULT CHARSET=latin1;
            """)
        except: raise LibraryException('Konnte Tabellen nicht erstellen', 
                critical=True)
    
    """
    Normalisiert einen String, escaped also ' und ".
    @param sqlString: Der zu normalisierende String.
    @type sqlString: String
    @return: Der normalisierte String.
    @rtype: String
    """
    def normalizeString(self, sqlString):
        returnString = sqlString.replace("\'", "\\'")
        returnString = returnString.replace('\"', '\\"')
        return returnString
